/*
 * Copyright (c) 2011 Vasile Vilvoiu. All rights reserved.
 *   
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * ferror.c - error handling routines
 */
#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include "ferror.h"
#include "version.h"

/* you fucking idiots! */
#if defined _WIN32 || defined _WIN64

#define snprintf _snprintf

#endif

/* error log entry struct */
typedef struct err_log_entry ERR_LOG_ENTRY;
struct err_log_entry {
	int id;
	int code;
	char *message;
	char *file;
	int line;
};

/* log size */
#define ERR_LOG_SIZE	1024

/* 
 * error log static vars
 * note that the error log is populated with entries from zero to ERR_LOG_SIZE-1 and then the
 *  position resets to zero, overwriting the oldest entries first. err_log_pos is the position in
 *  the entries array that should be written next and err_last_id is the total number of log entries
 *  written to the log.
 */
static char *err_log_file_name = NULL;
static ERR_LOG_ENTRY err_log[ERR_LOG_SIZE] = { {-1, 0, NULL, NULL, -1} };
static int err_log_pos = 0;
static int err_last_id = 0;

/*
 * err_init - initialise the error handling subsystem
 *  returns: error code
 *   filename(in): path to log file to use for error logging
 *   print_header(in): print header information in log file
 */
int err_init(const char* file_name, bool print_header) {

	FILE *f;
	int errc;

	/* check if file is avaliable for write */
	f = fopen(file_name, "w+");
	if (f == NULL) {
		return ERR_IO_FILE_WRITE_FAIL;
	} else {

		/* write header */
		if (print_header) {
			fprintf(f, "fett-mail server version %d.%d.%d\n", VER_MAJOR, VER_MINOR, VER_RELEASE);
			fprintf(f, "Copyright (c) 2011 Vasile Vilvoiu. All rights reserved.\n");
			fprintf(f, "Contact the author at vasi.vilvoiu@gmail.com\n");
			fprintf(f, "======================== ERROR LOG ====================\n");
		}

		/* close file */
		fclose(f);
	}

	/* free mem if previously allocated & store new file name */
	if (err_log_file_name) {
		free(err_log_file_name);
	}

	err_log_file_name = strdup(file_name);
	if (err_log_file_name == NULL) {
		return ERR_OUT_OF_MEM;
	}

	/* clear the log */
	if (errc = err_clear()) {
		return errc;
	}

	/* all went ok */
	return ERR_NONE;
}

/*
 * err_kill - deinitialise the error handling subsystem
 *  returns: error code
 */
int err_kill() {

	int errc;

	/* clear log */
	if (errc = err_clear()) {
		return errc;
	}

	/* all went ok */
	return ERR_NONE;
}

/*
 * err_clear_entry - clear error log entry
 *  returns: error code
 *   entry(in): pointer to log entry to clear
 */
static int err_clear_entry(ERR_LOG_ENTRY *entry) {

	/* check entry */
	if (entry == NULL) {
		return ERR_BAD_PTR;
	}

	/* free memory */
	if (entry->file) {
		free(entry->file);
	}

	if (entry->message) {
		free(entry->message);
	}

	/* reset entry */
	entry->id = -1;
	entry->code = 0;
	entry->line = -1;

	/* all went ok */
	return ERR_NONE;
}

/*
 * err_clear - clear the error log
 *  returns: error code
 */
int err_clear() {

	int log_index;

	/* free memory */
	for (log_index=0; log_index<ERR_LOG_SIZE; log_index++) {
		err_clear_entry(&err_log[log_index]);
	}

	/* reset counters */
	err_log_pos = 0;
	err_last_id = 0;

	/* all went ok */
	return ERR_NONE;
}

/*
 * err_error - register an error to the error log
 *  returns: the error code passed in the first parameter
 *   code(in): error code
 *   message(in): error message details
 *   file_name(in): name of file where error appeared
 *   line(in): line number where error appeared
 */
int err_error(int code, const char* message, const char* file_name, int line) {

	FILE *log;

	/* clear current entry */
	err_clear_entry(&err_log[err_log_pos]);

	/* register information */
	err_log[err_log_pos].id = ++err_last_id;
	err_log[err_log_pos].code = code;
	err_log[err_log_pos].line = line;

	err_log[err_log_pos].message = strdup(message);
	err_log[err_log_pos].file = strdup(file_name);

	/* increment position */
	if (++err_log_pos >= ERR_LOG_SIZE) {
		err_log_pos %= ERR_LOG_SIZE;
	}

	/* try to write to file */
	log = fopen(err_log_file_name, "a+");
	if (log != NULL) {
		char* text = err_get_text(code);

		fprintf(log, "[%d] %s (%s:%d): %s\n", code, text ? text : "\0", 
			file_name ? file_name : "\0", line, message ? message : "\0");

		if (text) {
			free(text);
		}

		fflush(log);
		fclose(log);
	}

	/* return code */
	return code;
}

/*
 * err_error_errorf - register an error with format string and varargs
 *  returns: the error code passed in the first parameter
 *   code(in): error code
 *   format(in): format string
 *   file_name(in): name of file where error appeared
 *   line(in): line number where error appeared
 *   args(in): arguments for format
 */

int err_errorf(int code, const char *format, const char *file_name, int line, ...) {

	char err_buffer[1025] = { 0 };
	int len = 0;

	/* write error to string */
	va_list argp;
	va_start(argp, line);
	len = vsnprintf(err_buffer, 1024, format, argp);
	va_end(argp);

	/* write string end and call error handling routine */
	if (len >= 0 && len <= 1024) {
		err_buffer[len] = 0;
		return err_error(code, err_buffer, file_name, line);
	} else {
		return err_error(code, format, file_name, line);
	}
}

/*
 * err_get_text - get error text associated with error code
 *  returns: error text string, or NULL if code is undefined
 *   code(in): error code
 */
char* err_get_text(int code) {
	
	char* error_text = NULL;

	/* get error text message */
	switch (code) {
		case ERR_NONE:
			error_text = strdup("no error");
			break;

		case ERR_GENERIC:
			error_text = strdup("generic error");
			break;

		case ERR_OUT_OF_MEM:
			error_text = strdup("out of virtual memory");
			break;

		case ERR_BAD_PTR:
			error_text = strdup("bad or null pointer");
			break;

		case ERR_IO_FILE_WRITE_FAIL:
			error_text = strdup("file write error");
			break;

		case ERR_IO_FILE_READ_FAIL:
			error_text = strdup("file read error");
			break;

		case ERR_CFG_PARSE_ERROR:
			error_text = strdup("config parse error");
			break;

		case ERR_CFG_BAD_VALUE:
			error_text = strdup("illegal configuration value");
			break;

		case ERR_WINSOCK_INIT_FAIL:
			error_text = strdup("winsock initialisation failed");
			break;

		case ERR_MAX_CONNECTIONS:
			error_text = strdup("maximum number of connections reached");
			break;

		case ERR_SOCKET_INVALID_PORT:
			error_text = strdup("port is invalid");
			break;

		case ERR_SOCKET_CREATE_FAIL:
			error_text = strdup("failed to create socket");
			break;

		case ERR_SOCKET_BIND_FAIL:
			error_text = strdup("failed to bind socket");
			break;

		case ERR_SOCKET_CLOSE_FAIL:
			error_text = strdup("failed to close socket");
			break;

		case ERR_SOCKET_LISTEN_FAIL:
			error_text = strdup("failed to listen on socket for connections");
			break;

		case ERR_SOCKET_ACCEPT_FAIL:
			error_text = strdup("failed to accept client connection");
			break;

		case ERR_THREAD_CREATE_FAIL:
			error_text = strdup("failed to create new thread");
			break;

		/*case ERR_:
			error_text = strdup("");
			break;*/

		default:
			break;
	}

	/* return error text (if any) */
	return error_text;
}